// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

// ************************************************************************* //
//                         avtBinaryMathExpression.C                         //
// ************************************************************************* //

#include <avtBinaryMathExpression.h>

#include <vtkCellData.h>
#include <vtkDataArray.h>
#include <vtkDataSet.h>
#include <vtkPointData.h>

#include <ExpressionException.h>


// ****************************************************************************
//  Method: avtBinaryMathExpression constructor
//
//  Purpose:
//      Defines the constructor.  Note: this should not be inlined in the
//      header because it causes problems for certain compilers.
//
//  Programmer: Hank Childs
//  Creation:   February 5, 2004
//
//  Modifications:
//
//    Alister Maguire, Tue Sep 24 11:15:10 MST 2019
//    Initialized canApplyToDirectDatabaseQOT. 
//
//    Alister Maguire, Mon Feb 24 14:25:20 MST 2020
//    Removed canApplyToDirectDatabaseQOT. It now defaults to true.
//
// ****************************************************************************

avtBinaryMathExpression::avtBinaryMathExpression()
{
    ;
}


// ****************************************************************************
//  Method: avtBinaryMathExpression destructor
//
//  Purpose:
//      Defines the destructor.  Note: this should not be inlined in the header
//      because it causes problems for certain compilers.
//
//  Programmer: Hank Childs
//  Creation:   February 5, 2004
//
// ****************************************************************************

avtBinaryMathExpression::~avtBinaryMathExpression()
{
    ;
}


// ****************************************************************************
//  Method: avtBinaryMathExpression::DeriveVariable
//
//  Purpose:
//      Derives a variable based on two variables in the input dataset.
//
//  Arguments:
//      in_ds     The first input dataset.
//
//  Returns:      The derived variable.  The calling class must free this
//                memory.
//
//  Programmer:   Sean Ahern
//  Creation:     Tue Jun 25 19:42:13 PDT 2002
//
//  Notes:
//      Sean Ahern, Fri Jun 14 11:52:33 PDT 2002
//      Since the centering that's stored in
//      GetInput()->GetInfo().GetAttributes().GetCentering() is not on a
//      per-variable basis, we can't rely on it for the centering
//      information.  Instead, get the scalars from the point and cell
//      data.  Whichever one is non-NULL is the one we want.
//
//  Modifications:
//
//    Hank Childs, Fri Nov 15 14:30:21 PST 2002
//    Allow for arbitrary data array types.
//
//    Kathleen Bonnell, Thu Jan  2 15:16:50 PST 2003 
//    Replace MakeObject() with NewInstance() to match new vtk api. 
//
//    Hank Childs, Thu Aug 14 13:40:20 PDT 2003
//    Allow the derived types to specify how many components there will be in
//    the output.
//
//    Hank Childs, Wed Dec 10 11:11:46 PST 2003
//    Do a better job of handling variables with different centerings.
//
//    Hank Childs, Thu Apr 22 11:11:33 PDT 2004
//    When the centerings are different, always use zonal.
//
//    Hank Childs, Thu Jan 20 17:32:13 PST 2005
//    Make cur_mesh accessible to derived types.
//
//    Hank Childs, Thu Jan 12 10:01:41 PST 2006
//    Make sure that all arrays are valid.
//
//    Hank Childs, Mon Jan 14 17:29:34 PST 2008
//    Add support for singletons.
//
// ****************************************************************************

vtkDataArray *
avtBinaryMathExpression::DeriveVariable(vtkDataSet *in_ds, int currentDomainsIndex)
{
    // Our first operand is in the active variable.  We don't know if it's
    // point data or cell data, so check which one is non-NULL.
    vtkDataArray *cell_data1 = in_ds->GetCellData()->GetArray(varnames[0]);
    vtkDataArray *point_data1 = in_ds->GetPointData()->GetArray(varnames[0]);
    vtkDataArray *data1 = NULL, *data2 = NULL;

    if (cell_data1 != NULL)
    {
        data1 = cell_data1;
        centering = AVT_ZONECENT;
    }
    else
    {
        data1 = point_data1;
        centering = AVT_NODECENT;
    }

    if (data1 == NULL)
    {
        EXCEPTION2(ExpressionException, outputVariableName, 
                   "An internal error occurred when calculating an expression."
                   "  Please contact a VisIt developer.");
    }

    // Get the second variable.
    bool ownData1 = false;
    bool ownData2 = false;
    if (centering == AVT_ZONECENT)
    {
        data2 = in_ds->GetCellData()->GetArray(varnames[1]);
        if (data2 == NULL)
        {
            data2 = in_ds->GetPointData()->GetArray(varnames[1]);
            if (data2 != NULL)
            {
                data2 = Recenter(in_ds, data2,AVT_NODECENT,outputVariableName);
                ownData2 = true;
            }
            else
            {
                EXCEPTION2(ExpressionException, outputVariableName, 
                           "Unable to locate variable");
            }
        }
    }
    else
    {
        data2 = in_ds->GetPointData()->GetArray(varnames[1]);
        if (data2 == NULL)
        {
            data2 = in_ds->GetCellData()->GetArray(varnames[1]);
            if (data2 != NULL)
            {
                // Recenter data1 so it will match data2.  We want both zonal.
                data1 = Recenter(in_ds, data1, AVT_NODECENT, outputVariableName);
                centering = AVT_ZONECENT;
                ownData1 = true;
            }
            else
            {
                EXCEPTION2(ExpressionException, outputVariableName, 
                           "Unable to locate variable");
            }
        }
    }

    //
    // Set up a VTK variable reflecting the calculated variable
    //
    int ncomps1 = data1->GetNumberOfComponents();
    int ncomps2 = data2->GetNumberOfComponents();
    int ncomps = GetNumberOfComponentsInOutput(ncomps1, ncomps2);
    int nvals  = data1->GetNumberOfTuples();
    if (nvals == 1)  // data1 is a singleton
        nvals = data2->GetNumberOfTuples();
         

    vtkDataArray *dv = CreateArray(data1, data2);
    dv->SetNumberOfComponents(ncomps);
    dv->SetNumberOfTuples(nvals);

    cur_mesh = in_ds;
    DoOperation(data1, data2, dv, ncomps, nvals);
    cur_mesh = NULL;

    if (GetOutput()->GetInfo().GetAttributes().GetVariableDimension()
        != ncomps)
    {
        GetOutput()->GetInfo().GetAttributes().SetVariableDimension(ncomps);
    }

    if (ownData1)
    {
        data1->Delete();
    }
    if (ownData2)
    {
        data2->Delete();
    }

    return dv;
}

// ****************************************************************************
// Method: PrecisionScore
//
// Purpose: 
//   Assign a "score" to the precision of VTK types so we can come up with
//   a winner when comparing 2 types.
//
// Arguments:
//   dt : The VTK data type.
//
// Returns:    A score for the input data type.
//
// Note:       
//
// Programmer: Brad Whitlock
// Creation:   Wed Apr 18 14:00:14 PDT 2012
//
// Modifications:
//   
// ****************************************************************************

static int
PrecisionScore(int dt)
{
    // Assign a score to an input precision so we can compare the precisions 
    // of the input data arrays to see which wins. This helps us return the 
    // "higher" type so operations like (int,double) would return double.
    int score = 0;
    switch(dt)
    {
    default:
    case VTK_VOID:           score = 0; break;
    case VTK_BIT:            score = 1; break;
    case VTK_CHAR:           score = 2; break;
    case VTK_UNSIGNED_CHAR:  score = 2; break;
    case VTK_SHORT:          score = 3; break;
    case VTK_UNSIGNED_SHORT: score = 3; break;
    case VTK_INT:            score = 4; break;
    case VTK_UNSIGNED_INT:   score = 4; break;
    case VTK_LONG:           score = 5; break;
    case VTK_UNSIGNED_LONG:  score = 5; break;
    case VTK_FLOAT:          score = 6; break;
    case VTK_DOUBLE:         score = 7; break;
    case VTK_ID_TYPE:        score = 5; break;
    case VTK_SIGNED_CHAR:    score = 2; break;
    }
    return score;
}

// ****************************************************************************
//  Method: avtBinaryMathExpression::CreateArray
//
//  Purpose:
//      Creates an array to write the output into.  Most derived types want
//      the array to be of the same form as the input.  Some (like logical
//      operators) always want them to be a specific type (like uchar).
//
//  Programmer: Hank Childs
//  Creation:   August 20, 2003
//
//  Modifications:
//    Brad Whitlock, Wed Apr 18 14:01:10 PDT 2012
//    Score the input types so we promote to the best one. This helps us when
//    we get inputs like (int, double), in which case we'd want to promote to
//    double.
//
// ****************************************************************************

vtkDataArray *
avtBinaryMathExpression::CreateArray(vtkDataArray *in1, vtkDataArray *in2)
{
    if(in1->GetDataType() == in2->GetDataType())
        return in1->NewInstance();

    int s1 = PrecisionScore(in1->GetDataType());
    int s2 = PrecisionScore(in2->GetDataType());

    if(s1 >= s2)
        return in1->NewInstance();

    return in2->NewInstance();
}


// ****************************************************************************
//  Method: avtBinaryMathExpression::GetVariableDimension
//
//  Purpose:
//      Determines what the variable dimension of the output is.
//
//  Programmer: Hank Childs
//  Creation:   August 19, 2005
//
// ****************************************************************************

int
avtBinaryMathExpression::GetVariableDimension(void)
{
    if (*(GetInput()) == NULL)
        return avtMultipleInputExpressionFilter::GetVariableDimension();
    if (varnames.size() != 2)
        return avtMultipleInputExpressionFilter::GetVariableDimension();

    avtDataAttributes &atts = GetInput()->GetInfo().GetAttributes();
    if (! atts.ValidVariable(varnames[0]))
        return avtMultipleInputExpressionFilter::GetVariableDimension();
    int ncomp1 = atts.GetVariableDimension(varnames[0]);

    if (! atts.ValidVariable(varnames[1]))
        return avtMultipleInputExpressionFilter::GetVariableDimension();
    int ncomp2 = atts.GetVariableDimension(varnames[1]);

    return GetNumberOfComponentsInOutput(ncomp1, ncomp2);
}


// ****************************************************************************
//  Method:  avtBinaryMathExpression::FilterUnderstandsTransformedRectMesh
//
//  Purpose:
//    If this filter returns true, this means that it correctly deals
//    with rectilinear grids having an implied transform set in the
//    data attributes.  It can do this conditionally if desired.
//
//  Arguments:
//    none
//
//  Programmer:  Jeremy Meredith
//  Creation:    February 15, 2007
//
// ****************************************************************************
bool
avtBinaryMathExpression::FilterUnderstandsTransformedRectMesh()
{
    // simple binary math operates only on variables and
    // shouldn't be directly affected by coordinates
    return true;
}


